"""DataUpdateCoordinator for the wallbox integration."""

from __future__ import annotations

from collections.abc import Callable
from datetime import timedelta
from http import HTTPStatus
import logging
from typing import Any, Concatenate

import requests
from wallbox import Wallbox

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import (
    CHARGER_CURRENCY_KEY,
    CHARGER_DATA_KEY,
    CHARGER_DATA_POST_L1_KEY,
    CHARGER_DATA_POST_L2_KEY,
    CHARGER_ECO_SMART_KEY,
    CHARGER_ECO_SMART_MODE_KEY,
    CHARGER_ECO_SMART_STATUS_KEY,
    CHARGER_ENERGY_PRICE_KEY,
    CHARGER_FEATURES_KEY,
    CHARGER_LOCKED_UNLOCKED_KEY,
    CHARGER_MAX_CHARGING_CURRENT_KEY,
    CHARGER_MAX_CHARGING_CURRENT_POST_KEY,
    CHARGER_MAX_ICP_CURRENT_KEY,
    CHARGER_PLAN_KEY,
    CHARGER_POWER_BOOST_KEY,
    CHARGER_STATUS_DESCRIPTION_KEY,
    CHARGER_STATUS_ID_KEY,
    CODE_KEY,
    CONF_STATION,
    DOMAIN,
    UPDATE_INTERVAL,
    ChargerStatus,
    EcoSmartMode,
)

_LOGGER = logging.getLogger(__name__)

# Translation of StatusId based on Wallbox portal code:
# https://my.wallbox.com/src/utilities/charger/chargerStatuses.js
CHARGER_STATUS: dict[int, ChargerStatus] = {
    0: ChargerStatus.DISCONNECTED,
    14: ChargerStatus.ERROR,
    15: ChargerStatus.ERROR,
    161: ChargerStatus.READY,
    162: ChargerStatus.READY,
    163: ChargerStatus.DISCONNECTED,
    164: ChargerStatus.WAITING,
    165: ChargerStatus.LOCKED,
    166: ChargerStatus.UPDATING,
    177: ChargerStatus.SCHEDULED,
    178: ChargerStatus.PAUSED,
    179: ChargerStatus.SCHEDULED,
    180: ChargerStatus.WAITING_FOR_CAR,
    181: ChargerStatus.WAITING_FOR_CAR,
    182: ChargerStatus.PAUSED,
    183: ChargerStatus.WAITING_IN_QUEUE_POWER_SHARING,
    184: ChargerStatus.WAITING_IN_QUEUE_POWER_SHARING,
    185: ChargerStatus.WAITING_IN_QUEUE_POWER_BOOST,
    186: ChargerStatus.WAITING_IN_QUEUE_POWER_BOOST,
    187: ChargerStatus.WAITING_MID_FAILED,
    188: ChargerStatus.WAITING_MID_SAFETY,
    189: ChargerStatus.WAITING_IN_QUEUE_ECO_SMART,
    193: ChargerStatus.CHARGING,
    194: ChargerStatus.CHARGING,
    195: ChargerStatus.CHARGING,
    196: ChargerStatus.DISCHARGING,
    209: ChargerStatus.LOCKED,
    210: ChargerStatus.LOCKED_CAR_CONNECTED,
}

type WallboxConfigEntry = ConfigEntry[WallboxCoordinator]


def _require_authentication[_WallboxCoordinatorT: WallboxCoordinator, **_P](
    func: Callable[Concatenate[_WallboxCoordinatorT, _P], Any],
) -> Callable[Concatenate[_WallboxCoordinatorT, _P], Any]:
    """Authenticate with decorator using Wallbox API."""

    def require_authentication(
        self: _WallboxCoordinatorT, *args: _P.args, **kwargs: _P.kwargs
    ) -> Any:
        """Authenticate using Wallbox API."""
        try:
            self.authenticate()
            return func(self, *args, **kwargs)
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == HTTPStatus.FORBIDDEN:
                raise ConfigEntryAuthFailed(
                    translation_domain=DOMAIN, translation_key="invalid_auth"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    return require_authentication


def _validate(wallbox: Wallbox) -> None:
    """Authenticate using Wallbox API."""
    try:
        wallbox.authenticate()
    except requests.exceptions.HTTPError as wallbox_connection_error:
        if wallbox_connection_error.response.status_code == 403:
            raise InvalidAuth(
                translation_domain=DOMAIN, translation_key="invalid_auth"
            ) from wallbox_connection_error
        raise ConnectionError from wallbox_connection_error


async def async_validate_input(hass: HomeAssistant, wallbox: Wallbox) -> None:
    """Get new sensor data for Wallbox component."""
    await hass.async_add_executor_job(_validate, wallbox)


class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
    """Wallbox Coordinator class."""

    config_entry: WallboxConfigEntry

    def __init__(
        self, hass: HomeAssistant, config_entry: WallboxConfigEntry, wallbox: Wallbox
    ) -> None:
        """Initialize."""
        self._station = config_entry.data[CONF_STATION]
        self._wallbox = wallbox

        super().__init__(
            hass,
            _LOGGER,
            config_entry=config_entry,
            name=DOMAIN,
            update_interval=timedelta(seconds=UPDATE_INTERVAL),
        )

    def authenticate(self) -> None:
        """Authenticate using Wallbox API."""
        self._wallbox.authenticate()

    @_require_authentication
    def _get_data(self) -> dict[str, Any]:
        """Get new sensor data for Wallbox component."""
        try:
            data: dict[str, Any] = self._wallbox.getChargerStatus(self._station)
            data[CHARGER_MAX_CHARGING_CURRENT_KEY] = data[CHARGER_DATA_KEY][
                CHARGER_MAX_CHARGING_CURRENT_KEY
            ]
            data[CHARGER_LOCKED_UNLOCKED_KEY] = data[CHARGER_DATA_KEY][
                CHARGER_LOCKED_UNLOCKED_KEY
            ]
            data[CHARGER_ENERGY_PRICE_KEY] = data[CHARGER_DATA_KEY][
                CHARGER_ENERGY_PRICE_KEY
            ]
            # Only show max_icp_current if power_boost is available in the wallbox unit:
            if (
                data[CHARGER_DATA_KEY].get(CHARGER_MAX_ICP_CURRENT_KEY, 0) > 0
                and CHARGER_POWER_BOOST_KEY
                in data[CHARGER_DATA_KEY][CHARGER_PLAN_KEY][CHARGER_FEATURES_KEY]
            ):
                data[CHARGER_MAX_ICP_CURRENT_KEY] = data[CHARGER_DATA_KEY][
                    CHARGER_MAX_ICP_CURRENT_KEY
                ]

            data[CHARGER_CURRENCY_KEY] = (
                f"{data[CHARGER_DATA_KEY][CHARGER_CURRENCY_KEY][CODE_KEY]}/kWh"
            )

            data[CHARGER_STATUS_DESCRIPTION_KEY] = CHARGER_STATUS.get(
                data[CHARGER_STATUS_ID_KEY], ChargerStatus.UNKNOWN
            )

            # Set current solar charging mode
            eco_smart_enabled = (
                data[CHARGER_DATA_KEY]
                .get(CHARGER_ECO_SMART_KEY, {})
                .get(CHARGER_ECO_SMART_STATUS_KEY)
            )

            eco_smart_mode = (
                data[CHARGER_DATA_KEY]
                .get(CHARGER_ECO_SMART_KEY, {})
                .get(CHARGER_ECO_SMART_MODE_KEY)
            )
            if eco_smart_mode is None:
                data[CHARGER_ECO_SMART_KEY] = EcoSmartMode.DISABLED
            elif eco_smart_enabled is False:
                data[CHARGER_ECO_SMART_KEY] = EcoSmartMode.OFF
            elif eco_smart_mode == 0:
                data[CHARGER_ECO_SMART_KEY] = EcoSmartMode.ECO_MODE
            elif eco_smart_mode == 1:
                data[CHARGER_ECO_SMART_KEY] = EcoSmartMode.FULL_SOLAR
            return data  # noqa: TRY300
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 429:
                raise UpdateFailed(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise UpdateFailed(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def _async_update_data(self) -> dict[str, Any]:
        """Get new sensor data for Wallbox component. Set update interval to be UPDATE_INTERVAL * #wallbox chargers configured, this is necessary due to rate limitations."""

        self.update_interval = timedelta(
            seconds=UPDATE_INTERVAL
            * max(len(self.hass.config_entries.async_loaded_entries(DOMAIN)), 1)
        )
        return await self.hass.async_add_executor_job(self._get_data)

    @_require_authentication
    def _set_charging_current(
        self, charging_current: float
    ) -> dict[str, dict[str, dict[str, Any]]]:
        """Set maximum charging current for Wallbox."""
        try:
            result = self._wallbox.setMaxChargingCurrent(
                self._station, charging_current
            )
            data = self.data
            data[CHARGER_MAX_CHARGING_CURRENT_KEY] = result[CHARGER_DATA_POST_L1_KEY][
                CHARGER_DATA_POST_L2_KEY
            ][CHARGER_MAX_CHARGING_CURRENT_POST_KEY]
            return data  # noqa: TRY300
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 403:
                raise InsufficientRights(
                    translation_domain=DOMAIN,
                    translation_key="insufficient_rights",
                    hass=self.hass,
                ) from wallbox_connection_error
            if wallbox_connection_error.response.status_code == 429:
                raise HomeAssistantError(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def async_set_charging_current(self, charging_current: float) -> None:
        """Set maximum charging current for Wallbox."""
        data = await self.hass.async_add_executor_job(
            self._set_charging_current, charging_current
        )
        self.async_set_updated_data(data)

    @_require_authentication
    def _set_icp_current(self, icp_current: float) -> dict[str, Any]:
        """Set maximum icp current for Wallbox."""
        try:
            result = self._wallbox.setIcpMaxCurrent(self._station, icp_current)
            data = self.data
            data[CHARGER_MAX_ICP_CURRENT_KEY] = result[CHARGER_MAX_ICP_CURRENT_KEY]
            return data  # noqa: TRY300
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 403:
                raise InsufficientRights(
                    translation_domain=DOMAIN,
                    translation_key="insufficient_rights",
                    hass=self.hass,
                ) from wallbox_connection_error
            if wallbox_connection_error.response.status_code == 429:
                raise HomeAssistantError(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def async_set_icp_current(self, icp_current: float) -> None:
        """Set maximum icp current for Wallbox."""
        data = await self.hass.async_add_executor_job(
            self._set_icp_current, icp_current
        )
        self.async_set_updated_data(data)

    @_require_authentication
    def _set_energy_cost(self, energy_cost: float) -> dict[str, Any]:
        """Set energy cost for Wallbox."""
        try:
            result = self._wallbox.setEnergyCost(self._station, energy_cost)
            data = self.data
            data[CHARGER_ENERGY_PRICE_KEY] = result[CHARGER_ENERGY_PRICE_KEY]
            return data  # noqa: TRY300
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 429:
                raise HomeAssistantError(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def async_set_energy_cost(self, energy_cost: float) -> None:
        """Set energy cost for Wallbox."""
        data = await self.hass.async_add_executor_job(
            self._set_energy_cost, energy_cost
        )
        self.async_set_updated_data(data)

    @_require_authentication
    def _set_lock_unlock(self, lock: bool) -> dict[str, dict[str, dict[str, Any]]]:
        """Set wallbox to locked or unlocked."""
        try:
            if lock:
                result = self._wallbox.lockCharger(self._station)
            else:
                result = self._wallbox.unlockCharger(self._station)
            data = self.data
            data[CHARGER_LOCKED_UNLOCKED_KEY] = result[CHARGER_DATA_POST_L1_KEY][
                CHARGER_DATA_POST_L2_KEY
            ][CHARGER_LOCKED_UNLOCKED_KEY]
            return data  # noqa: TRY300
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 403:
                raise InsufficientRights(
                    translation_domain=DOMAIN,
                    translation_key="insufficient_rights",
                    hass=self.hass,
                ) from wallbox_connection_error
            if wallbox_connection_error.response.status_code == 429:
                raise HomeAssistantError(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def async_set_lock_unlock(self, lock: bool) -> None:
        """Set wallbox to locked or unlocked."""
        data = await self.hass.async_add_executor_job(self._set_lock_unlock, lock)
        self.async_set_updated_data(data)

    @_require_authentication
    def _pause_charger(self, pause: bool) -> None:
        """Set wallbox to pause or resume."""
        try:
            if pause:
                self._wallbox.pauseChargingSession(self._station)
            else:
                self._wallbox.resumeChargingSession(self._station)
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 429:
                raise HomeAssistantError(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def async_pause_charger(self, pause: bool) -> None:
        """Set wallbox to pause or resume."""
        await self.hass.async_add_executor_job(self._pause_charger, pause)
        await self.async_request_refresh()

    @_require_authentication
    def _set_eco_smart(self, option: str) -> None:
        """Set wallbox solar charging mode."""
        try:
            if option == EcoSmartMode.ECO_MODE:
                self._wallbox.enableEcoSmart(self._station, 0)
            elif option == EcoSmartMode.FULL_SOLAR:
                self._wallbox.enableEcoSmart(self._station, 1)
            else:
                self._wallbox.disableEcoSmart(self._station)
        except requests.exceptions.HTTPError as wallbox_connection_error:
            if wallbox_connection_error.response.status_code == 429:
                raise HomeAssistantError(
                    translation_domain=DOMAIN, translation_key="too_many_requests"
                ) from wallbox_connection_error
            raise HomeAssistantError(
                translation_domain=DOMAIN, translation_key="api_failed"
            ) from wallbox_connection_error

    async def async_set_eco_smart(self, option: str) -> None:
        """Set wallbox solar charging mode."""

        await self.hass.async_add_executor_job(self._set_eco_smart, option)
        await self.async_request_refresh()


class InvalidAuth(HomeAssistantError):
    """Error to indicate there is invalid auth."""


class InsufficientRights(HomeAssistantError):
    """Error to indicate there are insufficient right for the user."""

    def __init__(
        self,
        *args: object,
        translation_domain: str | None = None,
        translation_key: str | None = None,
        translation_placeholders: dict[str, str] | None = None,
        hass: HomeAssistant,
    ) -> None:
        """Initialize exception."""
        super().__init__(
            self, *args, translation_domain, translation_key, translation_placeholders
        )
        self.hass = hass
        self._create_insufficient_rights_issue()

    def _create_insufficient_rights_issue(self) -> None:
        """Creates an issue for insufficient rights."""
        ir.create_issue(
            self.hass,
            DOMAIN,
            "insufficient_rights",
            is_fixable=False,
            severity=ir.IssueSeverity.ERROR,
            learn_more_url="https://www.home-assistant.io/integrations/wallbox/#troubleshooting",
            translation_key="insufficient_rights",
        )
